package com.xbts.system.module.payment.kit.plugin.bank;


import com.xbts.common.config.SDKConfig;
import com.xbts.common.constant.SDKConstants;
import com.xbts.common.core.domain.AjaxResult;
import com.xbts.common.exception.ServiceException;
import com.xbts.common.utils.CertUtil;
import com.xbts.common.utils.StringUtils;
import com.xbts.system.module.payment.domain.RefundLog;
import com.xbts.system.module.payment.kit.BankBase;
import com.xbts.system.module.payment.enums.CashierEnum;
import com.xbts.system.module.payment.kit.CashierSupport;
import com.xbts.system.module.payment.kit.AcpService;
import com.xbts.system.module.payment.kit.dto.PayParam;
import com.xbts.system.module.payment.kit.params.dto.CashierParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 银行卡支付
 *
 * @author Chopper
 * @version v1.0
 * 2021-02-20 10:14
 */
@Slf4j
@Component
public class BankTransferPlugin {


    // TODO 银行卡支付证书初始化，需要银联支付时打开
//    @PostConstruct
    public void init() {
        System.out.println("银联支付初始化............................");
        SDKConfig.getConfig().loadPropertiesFromSrc(); //从classpath加载acp_sdk.properties文件
        CertUtil.init();
    }

    @Value("${xbts.union.merId}")
    private String merId;

    private String redirectUrl = "http://www.baidu.com";

    /**
     * 收银台
     */
    @Autowired
    private CashierSupport cashierSupport;

    public void pay(HttpServletRequest request, HttpServletResponse response, PayParam payParam) {
        if (!payParam.getOrderType().equals(CashierEnum.ORDER.name())) {
            throw new ServiceException("余额只能用来购买服务器");
        }
        CashierParam cashierParam = cashierSupport.cashierParam(payParam);
        Double price = cashierParam.getPrice();
        if (price == null) {
            throw new ServiceException("订单金额不能为空");
        }
        BigDecimal amount = new BigDecimal(price);
        try {
            Map<String, String> requestData = new HashMap<String, String>();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
            String txnTime = sdf.format(new Date());

            /***银联全渠道系统，产品参数，除了encoding自行选择外其他不需修改***/
            requestData.put("version", BankBase.version);              //版本号，全渠道默认值
            requestData.put("encoding", BankBase.encoding);              //字符集编码，可以使用UTF-8,GBK两种方式
            requestData.put("signMethod", SDKConfig.getConfig().getSignMethod()); //签名方法
            requestData.put("txnType", "01");                          //交易类型 ，01：消费
            requestData.put("txnSubType", "01");                          //交易子类型， 01：自助消费
            // TODO 业务类型 bizType：在线网关000201，企业网银000202
//            if (StringUtils.isEmpty(payParam.getBizType())) {
//                requestData.put("bizType", "000201");
//            } else {
//                requestData.put("bizType", payParam.getBizType());
//            }
            // 新商户必传字段
//        //业务场景
            requestData.put("bizScene", "110001");         //业务场景，取值参考接口规范
            requestData.put("payeeAcctNm", "xx商户");            //收款方账户名称。
            requestData.put("payeeAcctNo", "12345678");            //收款方账号。
            requestData.put("payeeBankName", "xx行");          //收款银行名称。

            //渠道类型，这个字段区分B2C网关支付和手机wap支付；07：PC,平板  08：手机
            requestData.put("channelType", "07");

            /***商户接入参数***/
            requestData.put("merId", merId);                              //商户号码，请改成自己申请的正式商户号或者open上注册得来的777测试商户号
            requestData.put("accessType", "0");                          //接入类型，0：直连商户
            requestData.put("orderId", payParam.getSn());             //商户订单号，8-40位数字字母，不能含“-”或“_”，可以自行定制规则
            requestData.put("txnTime", txnTime);        //订单发送时间，取系统时间，格式为YYYYMMDDhhmmss，必须取当前时间，否则会报txnTime无效
            requestData.put("currencyCode", "156");                      //交易币种（境内商户一般是156 人民币）

            amount = amount.multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP);
            String txnAmt = amount.toString();
            requestData.put("txnAmt", txnAmt);                              //交易金额，单位分，不要带小数点
            //requestData.put("reqReserved", "透传字段");        		      //请求方保留域，如需使用请启用即可；透传字段（可以实现商户自定义参数的追踪）本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样返回，商户可以按需上传，长度为1-1024个字节。出现&={}[]符号时可能导致查询接口应答报文解析失败，建议尽量只传字母数字并使用|分割，或者可以最外层做一次base64编码(base64编码之后出现的等号不会导致解析失败可以不用管)。

            requestData.put("riskRateInfo", "{commodityName=" + "中酒云" + "}");

            //前台通知地址 （需设置为外网能访问 http https均可），支付成功后的页面 点击“返回商户”按钮的时候将异步通知报文post到该地址
            //如果想要实现过几秒中自动跳转回商户页面权限，需联系银联业务申请开通自动返回商户权限
            //异步通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 消费交易 商户通知
            requestData.put("frontUrl", BankBase.frontUrl);

            //后台通知地址（需设置为【外网】能访问 http https均可），支付成功后银联会自动将异步通知报文post到商户上送的该地址，失败的交易银联不会发送后台通知
            //后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 消费交易 商户通知
            //注意:1.需设置为外网能访问，否则收不到通知    2.http https均可  3.收单后台通知后需要10秒内返回http200或302状态码
            //    4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200，那么银联会间隔一段时间再次发送。总共发送5次，每次的间隔时间为0,1,2,4分钟。
            //    5.后台通知地址如果上送了带有？的参数，例如：http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签，否则将会验签失败
            requestData.put("backUrl", BankBase.backUrl);

            // 订单超时时间。
            // 超过此时间后，除网银交易外，其他交易银联系统会拒绝受理，提示超时。 跳转银行网银交易如果超时后交易成功，会自动退款，大约5个工作日金额返还到持卡人账户。
            // 此时间建议取支付时的北京时间加15分钟。
            // 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失败。
            requestData.put("payTimeout", new SimpleDateFormat("yyyyMMddHHmmss").format(new Date().getTime() + 15 * 60 * 1000));

            //////////////////////////////////////////////////
            //
            //       报文中特殊用法请查看 PCwap网关跳转支付特殊用法.txt
            //
            //////////////////////////////////////////////////

            /**请求参数设置完毕，以下对请求参数进行签名并生成html表单，将表单写入浏览器跳转打开银联页面**/
            Map<String, String> submitFromData = AcpService.sign(requestData, BankBase.encoding);  //报文中certId,signature的值是在signData方法中获取并自动赋值的，只要证书配置正确即可。

            String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl();  //获取请求银联的前台地址：对应属性文件acp_sdk.properties文件中的acpsdk.frontTransUrl
            String html = AcpService.createAutoFormHtml(requestFrontUrl, submitFromData, BankBase.encoding);   //生成自动跳转的Html表单

            log.info("打印请求HTML，此为请求报文，为联调排查问题的依据：" + html);
            //将生成的html写到浏览器中完成自动跳转打开银联支付页面；这里调用signData之后，将html写到浏览器跳转到银联页面之前均不能对html中的表单项的名称和值进行修改，如果修改会导致验签不通过
            response.getWriter().write(html);
        } catch (Exception e) {
            log.error("银联支付异常", e);
        }
    }

    public static String getOrderId() {
        return new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
    }

    public void refund(RefundLog refundLog) {
        try {
            // 线下退款日志记录
        } catch (Exception e) {
            log.error("线下收款错误", e);
        }
    }

    /**
     * 展示二维码扫描支付
     *
     * @param request  HttpServletRequest
     * @param payParam 支付参数
     * @return 二维码内容
     */
    public AjaxResult nativePay(HttpServletRequest request, PayParam payParam) {
        try {

        } catch (Exception e) {
            log.error("支付异常", e);
            throw new ServiceException("支付业务异常，请稍后重试");
        }
        return null;
    }

    /**
     * 支付订单
     * <p>
     * //     * @param order 订单
     */
    public void callBack(HttpServletRequest request, HttpServletResponse response) {

        try {
            log.info("BackRcvResponse接收后台通知开始");

            String encoding = request.getParameter(SDKConstants.param_encoding);
            // 获取银联通知服务器发送的后台通知参数
            Map<String, String> reqParam = getAllRequestParam(request);

            log.info(reqParam.toString());

            Map<String, String> valideData = null;
            if (null != reqParam && !reqParam.isEmpty()) {
                Iterator<Map.Entry<String, String>> it = reqParam.entrySet().iterator();
                valideData = new HashMap<String, String>(reqParam.size());
                while (it.hasNext()) {
                    Map.Entry<String, String> e = it.next();
                    String key = (String) e.getKey();
                    String value = (String) e.getValue();
                    value = new String(value.getBytes(encoding), encoding);
                    valideData.put(key, value);
                }
            }

            //重要！验证签名前不要修改reqParam中的键值对的内容，否则会验签不过
            if (!AcpService.validate(valideData, encoding)) {
                log.info("验证签名结果[失败].");
                //验签失败，需解决验签问题

            } else {
                log.info("验证签名结果[成功].");
                //【注：为了安全验签成功才应该写商户的成功处理逻辑】交易成功，更新商户订单状态

                String orderId = valideData.get("orderId"); //获取后台通知的数据，其他字段也可用类似方式获取
                String orderSn = orderId; //orderId其实存的是Sn

                String respCode = valideData.get("respCode");
                String txnAmt = valideData.get("txnAmt");
                BigDecimal txnAmount = (new BigDecimal(txnAmt)).multiply(new BigDecimal(0.01));

                String queryId = valideData.get("queryId");
                String traceTime = valideData.get("traceTime");
                String payCardNo = valideData.get("payCardNo");
                String payCardType = valideData.get("payCardType"); //支付卡类型
                String paymentMethodMethod; //PayPaymentMethod里面的method字段
                if (StringUtils.isEmpty(payCardType)) {
                    paymentMethodMethod = "UNION";  //对之前代码做兼容，如果没有支付卡类型的情况走默认
                } else {
                    paymentMethodMethod = "UNION-" + payCardType;
                }

                //判断respCode=00、A6后，对涉及资金类的交易，请再发起查询接口查询，确定交易成功后更新数据库。
                if ("00".equals(respCode)) {  // 00 交易成功

                    //todo 若交易成功
                } else if ("A6".equals(respCode)) {  // A6 部分成功

                }
            }
            log.info("BackRcvResponse接收后台通知结束");
            //返回给银联服务器http 200  状态码
            response.getWriter().print("ok");
        } catch (Exception e) {
            log.error("银联支付回调异常", e);
        }

    }


    /**
     * 获取请求参数中所有的信息
     *
     * @param request
     * @return
     */
    private static Map<String, String> getAllRequestParam(final HttpServletRequest request) {
        Map<String, String> res = new HashMap<String, String>();
        Enumeration<?> temp = request.getParameterNames();
        if (null != temp) {
            while (temp.hasMoreElements()) {
                String en = (String) temp.nextElement();
                String value = request.getParameter(en);
                res.put(en, value);
                //在报文上送时，如果字段的值为空，则不上送<下面的处理为在获取所有参数数据时，判断若值为空，则删除这个字段>
                //System.out.println("ServletUtil类247行  temp数据的键=="+en+"     值==="+value);
                if (null == res.get(en) || "".equals(res.get(en))) {
                    res.remove(en);
                }
            }
        }
        return res;
    }

    public void sendRedirect(HttpServletRequest request, HttpServletResponse response) {
        try {
            response.sendRedirect(redirectUrl);
        } catch (IOException e) {
            log.error("银联支付异常", e);
        }
    }
}
